package com.cans.lightning.business.pms.service.impl;

import com.cans.lightning.base.mapper.IBaseMapper;
import com.cans.lightning.base.service.impl.BaseServiceImpl;
import com.cans.lightning.business.lowcode.dto.AppDefinitionDto;
import com.cans.lightning.business.lowcode.dto.AppMenuDto;
import com.cans.lightning.business.lowcode.service.api.IAppDefinitionService;
import com.cans.lightning.business.lowcode.service.api.IAppMenuService;
import com.cans.lightning.business.pms.dao.SysMenuDao;
import com.cans.lightning.business.pms.dto.SysMenuDto;
import com.cans.lightning.business.pms.dto.SysMenuShowDto;
import com.cans.lightning.business.pms.dto.SysMenuTreeDto;
import com.cans.lightning.business.pms.entity.SysMenu;
import com.cans.lightning.business.pms.entity.SysRole;
import com.cans.lightning.business.pms.entity.SysUser;
import com.cans.lightning.business.pms.mapper.SysMenuMapper;
import com.cans.lightning.business.pms.mapper.SysMenuShowMapper;
import com.cans.lightning.business.pms.service.api.ISysMenuService;
import com.cans.lightning.business.pms.service.api.ISysUserService;
import com.cans.lightning.config.redis.RedisCache;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.tuple.Pair;
import org.beetl.sql.mapper.BaseMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 权限 - 菜单
 *
 * @author shencan
 * @date 2020/6/14 20:53
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class SysMenuServiceImpl extends BaseServiceImpl<SysMenu, SysMenuDto, String> implements ISysMenuService {

    @Resource
    private SysMenuDao sysMenuDao;
    @Resource
    private ISysUserService sysUserService;
    @Resource
    private RedisCache redisCache;
    @Resource
    private SysMenuMapper sysMenuMapper;
    @Resource
    private SysMenuShowMapper sysMenuShowMapper;
    @Resource
    private IAppMenuService appMenuService;
    @Resource
    private IAppDefinitionService appDefinitionService;


    @Override
    public List<SysMenuShowDto> getUserMenuTree() {
        SysUser currentUser = sysUserService.getCurrentUser();
        return this.getUserMenuTree(currentUser);
    }

    @Override
    public List<SysMenuShowDto> getUserMenuTree(SysUser currentUser) {
        String redisKey = "user_menu_tree:" + currentUser.getId();
        return getDbUserMenuTree(currentUser);
    }

    @Override
    public void removeUserMenuTree(SysUser currentUser) {
        String redisKey = "user_menu_tree:" + currentUser.getId();
        if (redisCache.hasKey(redisKey)) {
            redisCache.deleteObject(redisKey);
        }

    }

    @Override
    public void removeUserMenuTree() {
        SysUser currentUser = sysUserService.getCurrentUser();
        String redisKey = "user_menu_tree:" + currentUser.getId();
        if (redisCache.hasKey(redisKey)) {
            redisCache.deleteObject(redisKey);
        }

    }

    /**
     * 从数据库加载用户的菜单
     *
     * @param user
     * @return
     */
    private List<SysMenuShowDto> getDbUserMenuTree(SysUser user) {
        return this.buildMenuShowTree(this.getMenuIdsByUser(user));
    }


    private List<SysMenuShowDto> buildMenuShowTree(List<SysMenu> menus) {
        List<SysMenuShowDto> showDtoList = Lists.newArrayList();
        Pair<List<SysMenu>, Map<String, List<SysMenu>>> menuPar = this.getMenuPar(menus);
        for (SysMenu menu : menuPar.getKey()) {
            SysMenuShowDto sysMenuShowDto = sysMenuShowMapper.toDto(menu);
            sysMenuShowDto.setChildren(sysMenuShowMapper.toDtos(menuPar.getValue().get(menu.getId())));
            showDtoList.add(sysMenuShowDto);
        }
        // 加载APP菜单
        showDtoList.addAll(loadAppMenuList());
        return showDtoList;
    }

    private List<SysMenuShowDto> loadAppMenuList() {
        List<SysMenuShowDto> showDtoList = Lists.newArrayList();
        List<AppDefinitionDto> allToDto = appDefinitionService.findAllToDto();
        for (AppDefinitionDto appDefinitionDto : allToDto) {
            SysMenuShowDto sysMenuShowDto = new SysMenuShowDto().create(appDefinitionDto);
            List<AppMenuDto> appMenuDtoList = appMenuService.getUserAppMenu(appDefinitionDto.getId());
            if (CollectionUtils.isEmpty(appMenuDtoList)) {
                continue;
            }
            sysMenuShowDto.setChildren(menuDtoToMenuShow(appMenuDtoList));
            showDtoList.add(sysMenuShowDto);
        }
        return showDtoList;
    }

    private List<SysMenuShowDto> menuDtoToMenuShow(List<AppMenuDto> userAppMenu) {
        List<SysMenuShowDto> resList = Lists.newArrayList();
        for (AppMenuDto appMenu : userAppMenu) {
            resList.add(new SysMenuShowDto().create(appMenu));
        }
        return resList;
    }

    private List<SysMenuDto> buildMenuDtoTree(List<SysMenu> menus) {
        List<SysMenuDto> showDtoList = Lists.newArrayList();
        Pair<List<SysMenu>, Map<String, List<SysMenu>>> menuPar = this.getMenuPar(menus);
        for (SysMenu menu : menuPar.getKey()) {
            SysMenuDto sysMenuShowDto = sysMenuMapper.toDto(menu);
            sysMenuShowDto.setChildren(sysMenuMapper.toDtos(menuPar.getValue().get(menu.getId())));
            showDtoList.add(sysMenuShowDto);
        }
        return showDtoList;
    }

    private Pair<List<SysMenu>, Map<String, List<SysMenu>>> getMenuPar(List<SysMenu> menus) {
        if (CollectionUtils.isEmpty(menus)) {
            return Pair.of(Lists.newArrayList(), Maps.newHashMap());
        }
        List<SysMenu> rootMenu = menus.stream().filter(menu -> menu.getParentId() == null).collect(Collectors.toList());
        Map<String, List<SysMenu>> parentIdMap = menus.stream().filter(menu -> menu.getParentId() != null).collect(Collectors.groupingBy(SysMenu::getParentId));
        return Pair.of(rootMenu, parentIdMap);
    }

    /**
     * 获取用户下所有菜单ID
     *
     * @param user 用户
     * @return
     */
    private List<SysMenu> getMenuIdsByUser(SysUser user) {
        if (Objects.equals(user.getSuperMan(), 1)) {
            return this.sysMenuDao.createLambdaQuery().asc(SysMenu::getIdx).select();
        } else {
            return this.getMenusByRoles(user.getRoles());
        }
    }

    private List<SysMenu> getMenusByRoles(Set<SysRole> roles) {
        List<SysMenu> menus = Lists.newArrayList();
        if (roles == null) {
            return menus;
        }
        for (SysRole role : roles) {
            menus.addAll(role.getMenus());
        }
        return menus;
    }

    @Override
    public List<SysMenuTreeDto> getTree() {

        List<SysMenu> menuList = this.sysMenuDao.createLambdaQuery().andIsNull(SysMenu::getParentId).asc(SysMenu::getIdx).select();
        List<SysMenuTreeDto> resList = Lists.newArrayList();
        for (SysMenu sysMenu : menuList) {
            resList.add(this.addChildren(sysMenu));
        }
        return resList;
    }

    @Override
    public List<SysMenu> getByParentId(String parentId) {
        return sysMenuDao.createLambdaQuery().andEq(SysMenu::getParentId, parentId).select();
    }

    @Override
    public List<SysMenuDto> tableTree() {
        return this.buildMenuDtoTree(this.sysMenuDao.createLambdaQuery().asc(SysMenu::getIdx).select());
    }


    private List<SysMenu> findChildren(String id) {
        return sysMenuDao.createLambdaQuery().andEq(SysMenu::getParentId, id).select();
    }

    /**
     * 插入父级
     *
     * @param parentId 菜单
     * @param menuIds  有权限的菜单ID集合
     */
    private void getInsertParentMenu(String parentId, Set<String> menuIds) {
        if (parentId != null) {
            menuIds.add(parentId);
            this.getInsertParentMenu(parentId, menuIds);
        }
    }

    /**
     * 插入子集
     *
     * @param menu    菜单
     * @param menuIds 有权限的菜单ID集合
     */
    private void insertChildren(SysMenu menu, Set<String> menuIds) {

        List<SysMenu> childrenList = sysMenuDao.createLambdaQuery().andEq(SysMenu::getParentId, menu.getId()).select();

        if (childrenList != null && childrenList.size() > 0) {
            for (SysMenu sysMenu : childrenList) {
                // 加入权限
                menuIds.add(sysMenu.getId());
                insertChildren(sysMenu, menuIds);
            }
        }

    }

    @Override
    public void saveOrUpdate(SysMenu sysMenu) {
        super.saveOrUpdate(sysMenu);
        this.removeUserMenuTree();
    }

    private SysMenuTreeDto addChildren(SysMenu sysMenu) {
        SysMenuTreeDto item = new SysMenuTreeDto();
        // 名称
        item.setTitle("(" + sysMenu.getMenuCode() + ")" + sysMenu.getMenuName());
        // 主键
        item.setKey(sysMenu.getId());
        item.setValue(sysMenu.getId());

        List<SysMenu> childrenList = this.findChildren(sysMenu.getId());
        if (!CollectionUtils.isEmpty(childrenList)) {
            // 是否叶子节点
            item.setIsLeaf(false);
            List<SysMenuTreeDto> nextList = Lists.newArrayList();
            for (SysMenu childrenMenu : childrenList) {
                nextList.add(this.addChildren(childrenMenu));
            }
            item.setChildren(nextList);
        } else {
            item.setIsLeaf(true);
        }

        return item;
    }

    @Override
    public BaseMapper<SysMenu> getDaoImpl() {
        return sysMenuDao;
    }

    @Override
    public IBaseMapper<SysMenu, SysMenuDto, String> getMapperImpl() {
        return sysMenuMapper;
    }
}
